{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "animated-tabs", "type": "registry:component", "dependencies": [ "motion" ], "devDependencies": [], "registryDependencies": [], "files": [ { "path": "./components/ui/tab.tsx", "content": "'use client';\r\n\r\nimport React, {\r\n ReactNode,\r\n createContext,\r\n useContext,\r\n useState,\r\n isValidElement,\r\n useMemo,\r\n useCallback,\r\n} from 'react';\r\nimport { cn } from '@/lib/utils';\r\nimport { AnimatePresence, motion } from 'motion/react';\r\n\r\n// Improved TypeScript interfaces with more specific types\r\ninterface TabContextType {\r\n activeTab: string;\r\n setActiveTab: (value: string) => void;\r\n wobbly: boolean;\r\n hover: boolean;\r\n defaultValue: string;\r\n prevIndex: number;\r\n setPrevIndex: (value: number) => void;\r\n tabsOrder: string[];\r\n}\r\n\r\nconst TabContext = createContext(undefined);\r\n\r\n// Custom hook with memoization\r\nexport const useTabs = () => {\r\n const context = useContext(TabContext);\r\n if (!context) {\r\n throw new Error('useTabs must be used within a TabsProvider');\r\n }\r\n return context;\r\n};\r\n\r\n// Props interfaces with more specific types\r\ninterface TabsProviderProps {\r\n children: ReactNode;\r\n defaultValue: string;\r\n wobbly?: boolean;\r\n hover?: boolean;\r\n}\r\n\r\ninterface TabsBtnProps {\r\n children: ReactNode;\r\n className?: string;\r\n value: string;\r\n}\r\n\r\ninterface TabsContentProps {\r\n children: ReactNode;\r\n className?: string;\r\n value: string;\r\n yValue?: boolean;\r\n}\r\n\r\nexport const TabsProvider: React.FC = React.memo(\r\n ({ children, defaultValue, wobbly = true, hover = false }) => {\r\n // Use useCallback to memoize state setters\r\n const [activeTab, setActiveTab] = useState(defaultValue);\r\n const [prevIndex, setPrevIndex] = useState(0);\r\n\r\n // Memoize tabs order to prevent unnecessary recalculations\r\n const tabsOrder = useMemo(() => {\r\n return React.Children.toArray(children)\r\n .filter((child) => isValidElement(child) && child.type === TabsContent)\r\n .map((child) => (child as React.ReactElement).props.value);\r\n }, [children]);\r\n\r\n // Memoize context value to prevent unnecessary re-renders\r\n const contextValue = useMemo(\r\n () => ({\r\n activeTab,\r\n setActiveTab,\r\n wobbly,\r\n hover,\r\n defaultValue,\r\n setPrevIndex,\r\n prevIndex,\r\n tabsOrder,\r\n }),\r\n [\r\n activeTab,\r\n setActiveTab,\r\n wobbly,\r\n hover,\r\n defaultValue,\r\n prevIndex,\r\n tabsOrder,\r\n ]\r\n );\r\n\r\n return (\r\n {children}\r\n );\r\n }\r\n);\r\n\r\n// Memoized TabsBtn component\r\nexport const TabsBtn: React.FC = React.memo(\r\n ({ children, className, value }) => {\r\n const {\r\n activeTab,\r\n setPrevIndex,\r\n setActiveTab,\r\n defaultValue,\r\n hover,\r\n wobbly,\r\n tabsOrder,\r\n } = useTabs();\r\n\r\n // Use useCallback to memoize the click handler\r\n const handleClick = useCallback(() => {\r\n setPrevIndex(tabsOrder.indexOf(activeTab));\r\n setActiveTab(value);\r\n }, [setPrevIndex, tabsOrder, activeTab, setActiveTab, value]);\r\n\r\n return (\r\n hover && handleClick()}\r\n onMouseEnter={() => hover && handleClick()}\r\n onClick={handleClick}\r\n >\r\n {children}\r\n\r\n \r\n {activeTab === value && (\r\n <>\r\n \r\n\r\n {wobbly && (\r\n <>\r\n \r\n \r\n \r\n )}\r\n \r\n )}\r\n \r\n \r\n );\r\n }\r\n);\r\n\r\n// Memoized TabsContent component\r\nexport const TabsContent: React.FC = React.memo(\r\n ({ children, className, value, yValue }) => {\r\n const { activeTab, tabsOrder, prevIndex } = useTabs();\r\n\r\n // Memoize direction calculation\r\n const isForward = useMemo(\r\n () => tabsOrder.indexOf(activeTab) > prevIndex,\r\n [tabsOrder, activeTab, prevIndex]\r\n );\r\n\r\n return (\r\n \r\n {activeTab === value && (\r\n \r\n {children}\r\n \r\n )}\r\n \r\n );\r\n }\r\n);\r\n\r\n// Add display names for better debugging\r\nTabsProvider.displayName = 'TabsProvider';\r\nTabsBtn.displayName = 'TabsBtn';\r\nTabsContent.displayName = 'TabsContent';\r\n", "type": "registry:component" }, { "path": "./registry/components/tabs/preview-tab.tsx", "content": "'use client';\r\nimport React, { useState } from 'react';\r\nimport { motion, AnimatePresence } from 'motion/react';\r\nimport Image from 'next/image';\r\nimport { TabsBtn, TabsContent, TabsProvider } from '@/components/ui/tab';\r\nconst effectArr = [\r\n {\r\n id: 1,\r\n name: 'basic',\r\n },\r\n {\r\n id: 2,\r\n name: 'hover',\r\n },\r\n {\r\n id: 3,\r\n name: 'wobbly',\r\n },\r\n];\r\nfunction index() {\r\n const [checkEffect, setCheckEffect] = useState('basic');\r\n return (\r\n <>\r\n
\r\n {effectArr?.map((effect, index) => {\r\n return (\r\n <>\r\n setCheckEffect(effect?.name)}\r\n className={`py-1 px-3 rounded-md capitalize relative `}\r\n >\r\n {effect?.name}\r\n {checkEffect === effect.name && (\r\n \r\n )}\r\n \r\n \r\n );\r\n })}\r\n
\r\n\r\n
\r\n \r\n
\r\n
\r\n \r\n \r\n design\r\n \r\n \r\n \r\n \r\n collaborate\r\n \r\n \r\n \r\n \r\n share\r\n \r\n \r\n \r\n \r\n publish\r\n \r\n \r\n
\r\n
\r\n \r\n
\r\n \r\n
\r\n
\r\n \r\n
\r\n \r\n
\r\n
\r\n \r\n
\r\n \r\n
\r\n
\r\n \r\n
\r\n \r\n
\r\n
\r\n \r\n
\r\n \r\n );\r\n}\r\n\r\nexport default index;\r\n", "type": "registry:component" } ] }